大部分 domain 的 mapper 皆為 key 的轉換,而在此範例中使用的外部資源為 ActiveRecord
,他大部分的界面支援 Ruby 的原生物件 hash
,因此可以如下擴充 base class ,讓 mapper 裡專注在處理 entity hash key 對 schema hash key 的 mapping。
class Mapper < Boxenn::Repositories::RecordMapper
def build(hash)
result = batch_mapping(value: hash, mapper: map)
result = custom_mapping(input: hash, result: result) if respond_to?(:custom_mapping, :include_private)
result
end
def batch_mapping(value:, mapper:)
case value
when Array
value.map do |item|
mapping(hash: item, mapper: mapper)
end
when Hash
mapping(hash: value, mapper: mapper)
end
end
def mapping(hash:, mapper:)
result = {}
hash.each do |key, value|
next if !mapper.key?(key)
if mapper[key].is_a?(Array)
result[mapper[key].first] = batch_mapping(value: value, mapper: mapper[key][1])
else
result[mapper[key]] = value.is_a?(Hash) ? value.compact : value
end
end
result
end
end
# User
# input (entity 的 hash)
input = {
id_number: 'A123456789',
profile: {
name: '王小明',
gender: :male,
birthday: '1998/01/01',
email: 'abc@gamil.com',
},
}
# output (schema 的 hash)
output = {
id_number: 'A123456789',
user_profile: {
name: '王小明',
gender: :male,
birthdate: '1998/01/01',
email: 'abc@gamil.com',
age: 23,
}
}
# mapper
class UserMapper < Mapper
# 繼承此 Mapper 可以定義兩個 method, map 和 custom_mapping
# custom_mapping 只有在需要改值或做邏輯處理時才需要定義
private
# map 是用來將 entity hash 轉為 db schema hash
# 如果遇到 nested 的 hash 請使用 array,第一個值為 key, 後面則為對應 hash 裡的 key 配對
def map
{
id_number: :id_number,
profile: [
:user_profile,
{
name: :name,
gender: :gender,
birthday: :birthdate,
email: :email,
},
]
}
end
# custom_mapping 需傳入兩個參數, input 和 result
# input 為 entity 轉成的 hash
# result 為已經轉為 db schema key 的 hash
# 最後回傳的是再特製處理過的 db schema key 的 hash
def custom_mapping(input:, result:)
result[:user_profile][:age] = age(input[:profile][:birthday])
result
end
def age(birthdate)
today = Time.zone.today
gap = today.year - birthdate.year
gap = gap - 1 if (
birthdate.month > today.month or
(birthdate.month >= today.month and birthdate.day > today.day)
)
gap
end
end
mapper = UserMapper.new
mapper.build(input) # => output
下一篇會介紹包裝外部資源的 Source Wrapper。